using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Globalization;
using System.Text;
using System.Windows.Forms;
using Bordecal.ImportsSorter.Properties;

namespace Bordecal.ImportsSorter.Configuration
{
	/// <summary>
	/// Dialog form for modifying sorting preferences.
	/// </summary>
	internal sealed partial class SortingConfigurationForm : Form
	{
		/// <summary>
		/// Direction in which one may move a list item.
		/// </summary>
		private enum MoveDirection
		{
			Up,
			Down
		}

		/// <summary>
		/// Initializes a new instance of the SortingConfigurationForm class.
		/// </summary>
		public SortingConfigurationForm()
		{
			InitializeComponent();

			this.nudInterGroupLineCount.Minimum = Settings.InterGroupLineCountRange.Minimum;
			this.nudInterGroupLineCount.Maximum = Settings.InterGroupLineCountRange.Maximum;

			this.nudBlockDisplayDuration.Minimum = (int)Settings.BlockDisplayDurationRange.Minimum.TotalMilliseconds;
			this.nudBlockDisplayDuration.Maximum = (int)Settings.BlockDisplayDurationRange.Maximum.TotalMilliseconds;
		}

		/// <summary>
		/// Populates the form with data from the persisted user settings.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void SortingConfigurationForm_Load(object sender, EventArgs e)
		{
			Settings userSettings = Settings.Default;

			string[] sortingGroups = userSettings.SortingGroupsArray;

			this.lstSortingGroups.BeginUpdate();
			try
			{
				foreach (string groupPrefix in sortingGroups)
				{
					string prefix = groupPrefix.Trim();
					if (prefix.Length == 0)
						prefix = Resources.OtherNamespacesPlaceholder;
					this.lstSortingGroups.Items.Add(prefix);
				}
			}
			finally
			{
				this.lstSortingGroups.EndUpdate();
			}

			this.ToggleButtonEnabledState();

			this.nudInterGroupLineCount.Value = userSettings.InterGroupLineCount;
			this.chkDisplayImportsBlock.Checked = userSettings.ShowImportsBlockAfterSorting;
			this.nudBlockDisplayDuration.Value = (int)userSettings.BlockDisplayDuration.TotalMilliseconds;

			this.ToggleDisplayDurationEnabledState();

			this.SortingGroupsValidate();
			this.InterGroupLineCountValidate();
		}

		/// <summary>
		/// Displays the AddSortingGroupForm dialog for addition of a new sorting group namespace prefix.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void btnAddSortingGroup_Click(object sender, EventArgs e)
		{
			string newPrefix = AddSortingGroupForm.AddSortingGroup(this.SortingGroupsListToArray(), this);

			if (newPrefix != null)
			{
				newPrefix = newPrefix.Trim();
				if (newPrefix.Length == 0)
					newPrefix = Resources.OtherNamespacesPlaceholder;

				this.lstSortingGroups.Items.Add(newPrefix);

				this.SortingGroupsValidate();
				this.ToggleButtonEnabledState();
			}
		}

		/// <summary>
		/// Deletes a sorting group from the list.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void btnDeleteSortingGroup_Click(object sender, EventArgs e)
		{
			if (this.EnsureListSelection())
			{
				string confirmationMessage = string.Format(CultureInfo.InvariantCulture, Resources.DeleteListItemConfirmation,
					this.lstSortingGroups.SelectedItem.ToString());

				MessageBoxOptions options = (MessageBoxOptions)0;
				if (this.RightToLeft == RightToLeft.Yes)
					options = MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign;

				if (MessageBox.Show(this, confirmationMessage, null, MessageBoxButtons.YesNo, MessageBoxIcon.Question,
					MessageBoxDefaultButton.Button2, options) == DialogResult.Yes)
				{
					this.lstSortingGroups.Items.RemoveAt(this.lstSortingGroups.SelectedIndex);

					this.SortingGroupsValidate();
					this.ToggleButtonEnabledState();
				}
			}
		}

		/// <summary>
		/// Enables/disables the deletion and move up/down buttons based on whether there are any items
		/// present in the sorting groups list.
		/// </summary>
		private void ToggleButtonEnabledState()
		{
			bool enabled = (this.lstSortingGroups.Items.Count > 0);

			this.btnDeleteSortingGroup.Enabled = enabled;
			this.btnMoveGroupUp.Enabled = enabled;
			this.btnMoveGroupDown.Enabled = enabled;
		}

		/// <summary>
		/// Verifies whether there is an item selected in the sorting groups list. 
		/// If not, a message box is displayed instructing the user to select an item.
		/// </summary>
		/// <returns>A boolean value indicating whether a list item is selected.</returns>
		private bool EnsureListSelection()
		{
			bool retVal = false;

			if (this.lstSortingGroups.SelectedIndex >= 0)
			{
				retVal = true;
			}
			else
			{
				MessageBoxOptions options = (MessageBoxOptions)0;
				if (this.RightToLeft == RightToLeft.Yes)
					options = MessageBoxOptions.RtlReading | MessageBoxOptions.RightAlign;

				MessageBox.Show(this, Resources.SelectListItemWarning, null, MessageBoxButtons.OK, MessageBoxIcon.None,
					MessageBoxDefaultButton.Button1, options);
			}

			return retVal;
		}

		/// <summary>
		/// Moves an item up by one position in the sorting groups list.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void btnMoveGroupUp_Click(object sender, EventArgs e)
		{
			this.MoveGroupPrefix(MoveDirection.Up);
		}

		/// <summary>
		/// Moves an item down by one position in the sorting groups list.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void btnMoveGroupDown_Click(object sender, EventArgs e)
		{
			this.MoveGroupPrefix(MoveDirection.Down);
		}

		/// <summary>
		/// Moves an item up or down by one position in the sorting groups list.
		/// </summary>
		/// <remarks>
		/// Item movement "wraps" around the beginning or end of the list.
		/// </remarks>
		/// <param name="direction">The direction in which the item is to be moved.</param>
		private void MoveGroupPrefix(MoveDirection direction)
		{
			switch (direction)
			{
				case MoveDirection.Up:
				case MoveDirection.Down:
					break;
				default:
					throw new ArgumentOutOfRangeException("direction");
			}

			if (this.EnsureListSelection())
			{
				ListBox.ObjectCollection listItems = this.lstSortingGroups.Items;

				int oldIndex = this.lstSortingGroups.SelectedIndex;
				object focalItem = this.lstSortingGroups.SelectedItem;

				int newIndex = oldIndex;
				switch (direction)
				{
					case MoveDirection.Up:
						newIndex = oldIndex - 1;
						break;
					case MoveDirection.Down:
						newIndex = oldIndex + 1;
						break;
				}

				if (newIndex < 0)
				{
					newIndex = listItems.Count - 1;
				}
				else if (newIndex >= listItems.Count)
				{
					newIndex = 0;
				}

				listItems.RemoveAt(oldIndex);
				listItems.Insert(newIndex, focalItem);

				this.lstSortingGroups.SelectedIndex = newIndex;
			}
		}

		/// <summary>
		/// Validates and saves (if valid) the user settings when the OK button is clicked.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void btnOK_Click(object sender, EventArgs e)
		{
			if (this.SortingGroupsValidate() && this.InterGroupLineCountValidate() &&
				this.BlockDisplayDurationValidate())
			{
				Settings userSettings = Settings.Default;
				userSettings.SortingGroupsArray = this.SortingGroupsListToArray();
				userSettings.InterGroupLineCount = (int)this.nudInterGroupLineCount.Value;
				userSettings.ShowImportsBlockAfterSorting = this.chkDisplayImportsBlock.Checked;
				userSettings.BlockDisplayDuration = new TimeSpan(0, 0, 0, 0, (int)this.nudBlockDisplayDuration.Value);

				userSettings.Save();

				this.Close();
			}
		}

		/// <summary>
		/// Validates the user-specified inter-group line count.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void nudInterGroupLineCount_Validating(object sender, CancelEventArgs e)
		{
			if (!this.InterGroupLineCountValidate())
				e.Cancel = true;
		}

		/// <summary>
		/// Validates the user-specified inter-group line count.
		/// </summary>
		/// <returns>A boolean value indicating whether the data is valid.</returns>
		private bool InterGroupLineCountValidate()
		{
			string errorMessage = string.Empty;

			try
			{
				int testValue = (int)this.nudInterGroupLineCount.Value;
				if (!Settings.InterGroupLineCountValid(testValue))
					errorMessage = TemplatedResources.OutOfInclusiveRangeValidationMessage(Settings.InterGroupLineCountRange);
			}
			catch (ArgumentOutOfRangeException)
			{
				errorMessage = TemplatedResources.OutOfInclusiveRangeValidationMessage(Settings.InterGroupLineCountRange);
			}

			this.errorProvider.SetError(this.nudInterGroupLineCount, errorMessage);

			return (errorMessage.Length == 0);
		}

		/// <summary>
		/// Validates the user-specified block display duration.
		/// </summary>
		/// <returns>A boolean value indicating whether the data is valid.</returns>
		private bool BlockDisplayDurationValidate()
		{
			string errorMessage = string.Empty;

			try
			{
				int testValue = (int)this.nudBlockDisplayDuration.Value;
				if (!Settings.BlockDisplayDurationValid(new TimeSpan(0, 0, 0, 0, testValue)))
					errorMessage = TemplatedResources.OutOfInclusiveRangeValidationMessage(Settings.BlockDisplayDurationRange);
			}
			catch (ArgumentOutOfRangeException)
			{
				errorMessage = TemplatedResources.OutOfInclusiveRangeValidationMessage(Settings.BlockDisplayDurationRange);
			}

			this.errorProvider.SetError(this.nudBlockDisplayDuration, errorMessage);

			return (errorMessage.Length == 0);
		}

		/// <summary>
		/// Validates the user-specified sorting group list.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void lstSortingGroups_Validating(object sender, CancelEventArgs e)
		{
			if (!this.SortingGroupsValidate())
				e.Cancel = true;
		}

		/// <summary>
		/// Validates the user-specified sorting group list.
		/// </summary>
		/// <returns>A boolean value indicating whether the data is valid.</returns>
		private bool SortingGroupsValidate()
		{
			string errorMessage = string.Empty;
			string[] sortingGroups = this.SortingGroupsListToArray();

			if (!Settings.SortingGroupsLengthValid(sortingGroups))
			{
				errorMessage = TemplatedResources.ListTooLongValidationMessage(Settings.MaxGroupCount);
			}
			else
			{
				string[] duplicates = Settings.SortingGroupsDuplicates(sortingGroups);
				if (duplicates.Length > 0)
					errorMessage = TemplatedResources.DuplicatesInListValidationMessage(duplicates);
			}

			this.errorProvider.SetError(this.lstSortingGroups, errorMessage);

			return (errorMessage.Length == 0);
		}

		/// <summary>
		/// Copies the list of sorting groups from the list box Items collection to a one-dimensional string array.
		/// </summary>
		/// <returns>An array containing the same list of sorting group namespace prefixes present in the list box.</returns>
		private string[] SortingGroupsListToArray()
		{
			ListBox.ObjectCollection listItems = this.lstSortingGroups.Items;
			string[] retVal = new string[listItems.Count];

			string otherNamespacesPlaceholder = Resources.OtherNamespacesPlaceholder.Trim();
			for (int i = 0; i < listItems.Count; i++)
			{
				string item = listItems[i].ToString().Trim();
				if (item.Equals(otherNamespacesPlaceholder, StringComparison.OrdinalIgnoreCase))
					item = string.Empty;
				retVal[i] = item;
			}

			return retVal;
		}

		/// <summary>
		/// Validates the user-specified block display duration.
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void nudBlockDisplayDuration_Validating(object sender, CancelEventArgs e)
		{
			if (!this.BlockDisplayDurationValidate()) e.Cancel = true;
		}

		/// <summary>
		/// Triggers toggling of the display duration control
		/// </summary>
		/// <param name="sender">The source of the event.</param>
		/// <param name="e">The data for the event.</param>
		private void chkDisplayImportsBlock_CheckedChanged(object sender, EventArgs e)
		{
			this.ToggleDisplayDurationEnabledState();
		}

		/// <summary>
		/// Disables the display duration drop-down control iff the user has chosen not to display
		/// the imports block after sorting.
		/// </summary>
		private void ToggleDisplayDurationEnabledState()
		{
			this.nudBlockDisplayDuration.Enabled = this.chkDisplayImportsBlock.Checked;
		}
	}
}